using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace GameToolkit
{
    public class GizmosUtil
    {
        public static void DebugCube(Bounds bounds, Quaternion rot, Color color, float duration)
        {
            var min = bounds.center - rot * (bounds.size * 0.5f);
            var max = bounds.center + rot * (bounds.size * 0.5f);
            var right = rot * Vector3.right * bounds.size.x;
            var up = rot * Vector3.up * bounds.size.y;
            var forward = rot * Vector3.forward * bounds.size.z;
            Debug.DrawRay(min, forward, color, duration);
            Debug.DrawRay(min + right, forward, color, duration);
            Debug.DrawRay(min + up, forward, color, duration);
            Debug.DrawRay(max - forward, forward, color, duration);

            Debug.DrawRay(min, right, color, duration);
            Debug.DrawRay(min + up, right, color, duration);
            Debug.DrawRay(min + forward, right, color, duration);
            Debug.DrawRay(max - right, right, color, duration);

            Debug.DrawRay(min, up, color, duration);
            Debug.DrawRay(min + right, up, color, duration);
            Debug.DrawRay(min + forward, up, color, duration);
            Debug.DrawRay(max - up, up, color, duration);
        }

        public static void DebugCube(Transform space, Bounds bounds, Color color, float duration)
        {
            var mat = space == null ? Matrix4x4.identity : space.localToWorldMatrix;
            var min = mat.MultiplyPoint(bounds.min);
            var max = mat.MultiplyPoint(bounds.max);
            var right = mat.MultiplyVector(Vector3.right * bounds.size.x);
            var up = mat.MultiplyVector(Vector3.up * bounds.size.y);
            var forward = mat.MultiplyVector(Vector3.forward * bounds.size.z);
            Debug.DrawRay(min, forward, color, duration);
            Debug.DrawRay(min + right, forward, color, duration);
            Debug.DrawRay(min + up, forward, color, duration);
            Debug.DrawRay(max - forward, forward, color, duration);

            Debug.DrawRay(min, right, color, duration);
            Debug.DrawRay(min + up, right, color, duration);
            Debug.DrawRay(min + forward, right, color, duration);
            Debug.DrawRay(max - right, right, color, duration);

            Debug.DrawRay(min, up, color, duration);
            Debug.DrawRay(min + right, up, color, duration);
            Debug.DrawRay(min + forward, up, color, duration);
            Debug.DrawRay(max - up, up, color, duration);

        }

        public static void DrawFrustum(FrustumPlanes frustum)
        {
            if (frustum.zFar <= 0)
                return;
            var p0 = new Vector3(frustum.left, frustum.top, frustum.zFar);
            var p1 = new Vector3(frustum.right, frustum.top, frustum.zFar);
            var p2 = new Vector3(frustum.right, frustum.bottom, frustum.zFar);
            var p3 = new Vector3(frustum.left, frustum.bottom, frustum.zFar);
            Gizmos.DrawLine(p0, p1);
            Gizmos.DrawLine(p1, p2);
            Gizmos.DrawLine(p2, p3);
            Gizmos.DrawLine(p3, p0);
            var f = frustum.zNear > 0 && frustum.zNear < frustum.zFar ? frustum.zNear / frustum.zFar : 0;
            f = 1 - f;
            Gizmos.DrawRay(p0, -p0 * f);
            Gizmos.DrawRay(p1, -p1 * f);
            Gizmos.DrawRay(p2, -p2 * f);
            Gizmos.DrawRay(p3, -p3 * f);
            f = 1 - f;
            if (f > 0)
            {
                p0 *= f;
                p1 *= f;
                p2 *= f;
                p3 *= f;
                Gizmos.DrawLine(p0, p1);
                Gizmos.DrawLine(p1, p2);
                Gizmos.DrawLine(p2, p3);
                Gizmos.DrawLine(p3, p0);
            }
        }

        public static Vector4 GetFrustumClip(Camera cam)
        {
            Vector4 frustumClip;
            frustumClip.z = cam.nearClipPlane;
            frustumClip.w = cam.farClipPlane;// cam.farClipPlane;
            frustumClip.y = cam.farClipPlane * Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad);
            frustumClip.x = frustumClip.y * (float)cam.pixelWidth / cam.pixelHeight;
            return frustumClip;
        }

        public static void DrawFrustum(Camera cam)
        {
            Vector4 frustumClip;
            frustumClip.z = cam.nearClipPlane;
            frustumClip.w = cam.farClipPlane;// cam.farClipPlane;
            frustumClip.y = cam.farClipPlane * Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad);
            frustumClip.x = frustumClip.y * (float)cam.pixelWidth / cam.pixelHeight;
            Gizmos.matrix = cam.transform.localToWorldMatrix;
            DrawFrustum(frustumClip);
        }

        public static void DrawFrustum(Vector4 clip)
        {
            var dir1 = Vector3.forward * clip.w - Vector3.right * clip.x - Vector3.up * clip.y;
            var dir2 = Vector3.forward * clip.w + Vector3.right * clip.x - Vector3.up * clip.y;
            var dir3 = Vector3.forward * clip.w + Vector3.right * clip.x + Vector3.up * clip.y;
            var dir4 = Vector3.forward * clip.w - Vector3.right * clip.x + Vector3.up * clip.y;
            if (clip.z > 0)
            {
                var f = clip.z / clip.w;
                Gizmos.DrawLine(dir1, dir1 * f);
                Gizmos.DrawLine(dir2, dir2 * f);
                Gizmos.DrawLine(dir3, dir3 * f);
                Gizmos.DrawLine(dir4, dir4 * f);

                Gizmos.DrawLine(dir1 * f, dir2 * f);
                Gizmos.DrawLine(dir2 * f, dir3 * f);
                Gizmos.DrawLine(dir3 * f, dir4 * f);
                Gizmos.DrawLine(dir4 * f, dir1 * f);
            }
            else
            {
                Gizmos.DrawRay(Vector3.zero, dir1);
                Gizmos.DrawRay(Vector3.zero, dir2);
                Gizmos.DrawRay(Vector3.zero, dir3);
                Gizmos.DrawRay(Vector3.zero, dir4);
            }
            Gizmos.DrawLine(dir1, dir2);
            Gizmos.DrawLine(dir2, dir3);
            Gizmos.DrawLine(dir3, dir4);
            Gizmos.DrawLine(dir4, dir1);
        }

        public static void DebugAxis(Vector3 position, Transform parent = null, float size = 1, float duration = 1)
        {
            if (parent != null)
                position = parent.localToWorldMatrix.MultiplyPoint(position);
            var right = parent == null ? Vector3.right : parent.right;
            var up = parent == null ? Vector3.up : parent.up;
            var forward = parent == null ? Vector3.forward : parent.forward;
            Debug.DrawRay(position, right, Color.red, duration);
            Debug.DrawRay(position, up, Color.green, duration);
            Debug.DrawRay(position, forward, Color.blue, duration);
        }

        public static void DrawWiredCube(Vector3 center, Vector3 size, float bodyTransparency = 0.5f)
        {
            Color color = Gizmos.color;
            float a = color.a;
            color.a = a * bodyTransparency;
            Gizmos.color = color;
            Gizmos.DrawCube(center, size);
            color.a = a;
            Gizmos.color = color;
            Gizmos.DrawWireCube(center, size);
        }

        public static float LodArc(Vector3 pos)
        {
#if UNITY_EDITOR
            if (UnityEditor.SceneView.currentDrawingSceneView != null)
            {
                var cam = UnityEditor.SceneView.currentDrawingSceneView.camera;
                var dis = Vector3.SqrMagnitude(cam.transform.position - pos);
                return Mathf.Clamp(dis * 0.03f, 1, 3);
            }
#endif
            return 1;
        }

        public static void DrawArc(Vector3 center, Vector3 direction, Vector3 normal, float angle, float deltaAngle = 10f, bool drawSide = true)
        {
            if (angle < 1 || direction.sqrMagnitude <= 0.00001f)
            {
                Gizmos.DrawRay(center, direction);
                return;
            }
            float sides = Mathf.Ceil(angle / (LodArc(Gizmos.matrix.MultiplyPoint(center)) * deltaAngle));
            deltaAngle = angle / sides;
            Quaternion rot = Quaternion.AngleAxis(-angle * 0.5f, normal);
            Vector3 p0 = rot * direction;
            if (drawSide)
                Gizmos.DrawRay(center, p0);
            rot = Quaternion.AngleAxis(deltaAngle, normal);
            Vector3 p1 = p0;
            for (int i = 0; i < sides; i++)
            {
                p1 = rot * p0;
                Gizmos.DrawLine(center + p0, center + p1);
                p0 = p1;
            }
            if (drawSide)
                Gizmos.DrawRay(center, p1);
        }

        public static void DrawArc(Vector3 center, Vector3 start, Vector3 end, Vector3 normal, float deltaAngle = 10f, bool drawSide = true)
        {
            Vector3 dir1 = start - center;
            Vector3 dir2 = end - center;
            if (drawSide)
            {
                Gizmos.DrawLine(center, start);
                Gizmos.DrawLine(center, end);
            }
            float degree = Vector3.Angle(dir1, dir2);
            if (degree < 1)
                return;
            float len1 = dir1.magnitude;
            dir1 = dir1.normalized;
            float len2 = dir2.magnitude;
            dir2 = dir2.normalized;
            float sides = Mathf.Ceil(degree / (LodArc(Gizmos.matrix.MultiplyPoint(center)) * deltaAngle));
            deltaAngle = degree / sides;
            Quaternion rot;
            Vector3 p0 = start;
            Vector3 p1 = p0;
            Vector3 dir;
            for (int i = 0; i < sides; i++)
            {
                rot = Quaternion.AngleAxis(i * deltaAngle, normal);
                dir = rot * dir1;
                dir *= Mathf.Lerp(len1, len2, i * deltaAngle / degree);
                p1 = center + dir;
                Gizmos.DrawLine(p0, p1);
                p0 = p1;
            }
        }

        public static void DrawArrow(Vector3 from, Vector3 to, float size = 0.1f)
        {
            Gizmos.DrawLine(from, to);
            var dir = to - from;
#if UNITY_EDITOR
            var cam = UnityEditor.SceneView.currentDrawingSceneView.camera;
            var nor = cam.transform.forward;
#else
            var nor = Vector3.up + Vector3.forward;
#endif
            var off = Vector3.Cross(dir, nor).normalized;
            dir = dir.normalized * 2f;
            Gizmos.DrawRay(to, (off - dir) * size);
            Gizmos.DrawRay(to, (-dir - off) * size);
        }

        public static void DrawCylider(Vector3 from, Vector3 to, float radius, float deltaAngle = 15f)
        {
#if UNITY_EDITOR
            var mat = Gizmos.matrix;
            var cam = UnityEditor.SceneView.currentDrawingSceneView.camera;
            Gizmos.matrix = Matrix4x4.identity;
            from = mat.MultiplyPoint(from);
            to = mat.MultiplyPoint(to);
            radius = mat.MultiplyVector(Vector3.right).magnitude * radius;
            // var forward = from - cam.transform.position;
            var dir = Vector3.Cross(to - from, from - cam.transform.position).normalized * radius;
            var dir2 = Vector3.Cross(to - from, to - cam.transform.position).normalized * radius;
            var n = (to - from).normalized;
            DrawArc(from, dir, n, 360f, deltaAngle, false);
            DrawArc(to, dir2, n, 360f, deltaAngle, false);
            Gizmos.DrawLine(from + dir, to + dir2);
            Gizmos.DrawLine(from - dir, to - dir2);
            Gizmos.DrawLine(from - dir, from + dir);
            Gizmos.matrix = mat;
#endif
        }

        public static void DrawCapsule(Vector3 center, float radius, float height, int direction)
        {
            float cylen = height - radius * 2;
            Vector3 p0, p1, p2, p3;
            switch (direction)
            {
                case 0:
                    p0 = center - Vector3.right * cylen * 0.5f + Vector3.up * radius;
                    p1 = p0 + cylen * Vector3.right;
                    p2 = p0 + Vector3.down * radius * 2;
                    p3 = p2 + cylen * Vector3.right;
                    Gizmos.DrawLine(p0, p1);
                    Gizmos.DrawLine(p2, p3);
                    DrawArc(p0 + Vector3.down * radius, p0, p2, Vector3.forward, 5, false);
                    DrawArc(p1 + Vector3.down * radius, p1, p3, Vector3.back, 5, false);

                    p0 = center - Vector3.right * cylen * 0.5f + Vector3.forward * radius;
                    p1 = p0 + cylen * Vector3.right;
                    p2 = p0 + Vector3.back * radius * 2;
                    p3 = p2 + cylen * Vector3.right;
                    Gizmos.DrawLine(p0, p1);
                    Gizmos.DrawLine(p2, p3);
                    DrawArc(p0 + Vector3.back * radius, p0, p2, Vector3.down, 5, false);
                    DrawArc(p1 + Vector3.back * radius, p1, p3, Vector3.up, 5, false);
                    break;
                case 1:
                    p0 = center + Vector3.down * cylen * 0.5f + Vector3.left * radius;
                    p1 = p0 + cylen * Vector3.up;

                    p2 = p0 + Vector3.right * radius * 2;
                    p3 = p2 + cylen * Vector3.up;
                    Gizmos.DrawLine(p0, p1);
                    Gizmos.DrawLine(p2, p3);
                    DrawArc(p0 + Vector3.right * radius, p0, p2, Vector3.forward, 5, false);
                    DrawArc(p1 + Vector3.right * radius, p1, p3, Vector3.back, 5, false);

                    p0 = center + Vector3.down * cylen * 0.5f + Vector3.back * radius;
                    p1 = p0 + cylen * Vector3.up;

                    p2 = p0 + Vector3.forward * radius * 2;
                    p3 = p2 + cylen * Vector3.up;
                    Gizmos.DrawLine(p0, p1);
                    Gizmos.DrawLine(p2, p3);
                    DrawArc(p0 + Vector3.forward * radius, p0, p2, Vector3.left, 5, false);
                    DrawArc(p1 + Vector3.forward * radius, p1, p3, Vector3.right, 5, false);
                    break;
                case 2:
                    p0 = center + Vector3.back * cylen * 0.5f + Vector3.left * radius;
                    p1 = p0 + cylen * Vector3.forward;

                    p2 = p0 + Vector3.right * radius * 2;
                    p3 = p2 + cylen * Vector3.forward;
                    Gizmos.DrawLine(p0, p1);
                    Gizmos.DrawLine(p2, p3);
                    DrawArc(p0 + Vector3.right * radius, p0, p2, Vector3.down, 5, false);
                    DrawArc(p1 + Vector3.right * radius, p1, p3, Vector3.up, 5, false);

                    p0 = center + Vector3.back * cylen * 0.5f + Vector3.down * radius;
                    p1 = p0 + cylen * Vector3.forward;

                    p2 = p0 + Vector3.up * radius * 2;
                    p3 = p2 + cylen * Vector3.forward;
                    Gizmos.DrawLine(p0, p1);
                    Gizmos.DrawLine(p2, p3);
                    DrawArc(p0 + Vector3.up * radius, p0, p2, Vector3.right, 5, false);
                    DrawArc(p1 + Vector3.up * radius, p1, p3, Vector3.left, 5, false);
                    break;
                default:
                    break;
            }
        }

        public static void DrawCapsuleCollider(CapsuleCollider cap, float scale = 1)
        {
            DrawCapsule(cap.center, cap.radius * scale, cap.height * scale, cap.direction);
        }

        public static void MarkTransform(Transform trans, float size)
        {
            Gizmos.matrix = Matrix4x4.identity;
            Gizmos.color = Color.red;
            Gizmos.DrawRay(trans.position, trans.right * size);
            Gizmos.color = Color.blue;
            Gizmos.DrawRay(trans.position, trans.forward * size);
            Gizmos.color = Color.green;
            Gizmos.DrawRay(trans.position, trans.up * size);
        }

        public static void MarkLine(Vector3 worldPos, float length, float rad)
        {
#if UNITY_EDITOR
            UnityEditor.SceneView scene = UnityEditor.SceneView.currentDrawingSceneView;
            if (!scene || !scene.camera)
                return;
            Matrix4x4 defM = Gizmos.matrix;
            Gizmos.matrix = scene.camera.cameraToWorldMatrix;

            Vector3 p = scene.camera.worldToCameraMatrix.MultiplyPoint(worldPos);
            Vector3 sp = scene.camera.WorldToScreenPoint(worldPos);
            Vector3 sp2 = scene.camera.WorldToScreenPoint(Gizmos.matrix.MultiplyPoint(p + Vector3.right));
            float pixScale = 1f / Vector3.Distance(sp, sp2);

            Vector3 p1;
            Vector2 off = new Vector2(Mathf.Sin(rad), Mathf.Cos(rad));
            off *= length * pixScale;
            p1.x = p.x + off.x;
            p1.y = p.y + off.y;
            p1.z = p.z;
            Gizmos.DrawLine(p, p1);
            Gizmos.matrix = defM;
#endif
        }

        public static void DebugInScene(Vector3 worldPos, float size = 2, float duration = 1)
        {
            Debug.DrawRay(worldPos - Vector3.right * size * 0.5f, Vector3.right * size, Color.red, duration);
            Debug.DrawRay(worldPos - Vector3.up * size * 0.5f, Vector3.up * size, Color.green, duration);
            Debug.DrawRay(worldPos - Vector3.forward * size * 0.5f, Vector3.forward * size, Color.blue, duration);
        }

        public static void MarkInScene(Vector3 worldPos, float pixel = 10, float rad = 0)
        {
#if UNITY_EDITOR
            UnityEditor.SceneView scene = UnityEditor.SceneView.currentDrawingSceneView;
            if (!scene || !scene.camera)
                return;
            Matrix4x4 defM = Gizmos.matrix;
            Gizmos.matrix = scene.camera.cameraToWorldMatrix;

            Vector3 p = scene.camera.worldToCameraMatrix.MultiplyPoint(worldPos);
            Vector3 sp = scene.camera.WorldToScreenPoint(worldPos);
            Vector3 sp2 = scene.camera.WorldToScreenPoint(Gizmos.matrix.MultiplyPoint(p + Vector3.right));
            float pixScale = 1f / Vector3.Distance(sp, sp2);

            Vector3 p1;
            Vector2 off = new Vector2(Mathf.Sin(rad), Mathf.Cos(rad));
            off *= pixel * pixScale;
            p1.x = p.x + off.x;
            p1.y = p.y + off.y;
            p1.z = p.z;
            Vector3 p2 = p1;
            p2.x = p.x * 2f - p1.x;
            p2.y = p.y * 2f - p1.y;
            Gizmos.DrawLine(p1, p2);
            p1.x = p.x + off.y;
            p1.y = p.y - off.x;
            p2.x = p.x * 2f - p1.x;
            p2.y = p.y * 2f - p1.y;
            Gizmos.DrawLine(p1, p2);
            Gizmos.matrix = defM;
#endif
        }

        public static void MarkAxisSystem(float size)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawRay(Vector3.zero, Vector3.right * size);
            Gizmos.color = Color.green;
            Gizmos.DrawRay(Vector3.zero, Vector3.up * size);
            Gizmos.color = Color.blue;
            Gizmos.DrawRay(Vector3.zero, Vector3.forward * size);
        }


        public static void MarkAxisSystem(Vector3 size)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawRay(Vector3.zero, Vector3.right * size.x);
            Gizmos.color = Color.green;
            Gizmos.DrawRay(Vector3.zero, Vector3.up * size.y);
            Gizmos.color = Color.blue;
            Gizmos.DrawRay(Vector3.zero, Vector3.forward * size.z);
        }

        public static Matrix4x4 GetWorldToScreenMatrix(Camera cam, bool startAtTop)
        {
            var matrix = cam.projectionMatrix * cam.worldToCameraMatrix;
            var size = new Vector2(cam.pixelWidth * 0.5f, cam.pixelHeight * 0.5f);
            Matrix4x4 tos;
            if (startAtTop)
            {
                tos = new Matrix4x4(new Vector4(size.x, 0, 0, 0), new Vector4(0, -size.y, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(size.x, size.y, 0, 1));
            }
            else
            {
                tos = new Matrix4x4(new Vector4(size.x, 0, 0, 0), new Vector4(0, size.y, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(size.x, size.y, 0, 1));
            }
            return tos * matrix;
        }

        //public static float GetHandleSize(Matrix4x4 projection, Matrix4x4 view, Vector3 position, float pixelsPerPoint)
        //{
        //    //Camera current = Camera.current;
        //    position = Handles.matrix.MultiplyPoint(position);
        //    //Transform transform = current.transform;
        //    Vector3 position2 = view.MultiplyPoint(Vector3.zero);// transform.position;
        //    float z = Vector3.Dot(position - position2, view.MultiplyVector(new Vector3(0f, 0f, 1f)));
        //    Vector3 vector = projection.MultiplyPoint(position2 + view.MultiplyVector(new Vector3(0f, 0f, z)));
        //    Vector3 vector2 = projection.MultiplyPoint(position2 + view.MultiplyVector(new Vector3(1f, 0f, z)));
        //    float magnitude = (vector - vector2).magnitude;
        //    return 80f / Mathf.Max(magnitude, 0.0001f) * pixelsPerPoint;
        //}

        //屏幕像素大小与本地单位大小比值
        public static float FactorToPixel(Camera camera, Transform trans, Vector3 localPos = new Vector3())
        {
            Vector3 lp1 = trans ? trans.localToWorldMatrix.MultiplyPoint(localPos) : localPos;

            Vector3 sp1 = camera.WorldToScreenPoint(lp1);
            Vector3 sp2 = camera.ScreenToWorldPoint(sp1 + Vector3.right);
            Vector3 lp2 = trans ? trans.worldToLocalMatrix.MultiplyPoint(sp2) : sp2;
            float dis = Vector3.Distance(lp2, localPos);
            return dis > 0f ? 1f / dis : 0f;
        }

        //本地单位大小与屏幕像素大小比值
        public static float FactorFromPixel(Camera camera, Transform trans, Vector3 localPos = new Vector3())
        {
            Vector3 lp1 = trans ? trans.localToWorldMatrix.MultiplyPoint(localPos) : localPos;

            Vector3 sp1 = camera.WorldToScreenPoint(lp1);
            Vector3 sp2 = camera.ScreenToWorldPoint(sp1 + Vector3.right);
            Vector3 lp2 = trans ? trans.worldToLocalMatrix.MultiplyPoint(sp2) : sp2;
            float dis = Vector3.Distance(lp2, localPos);
            return dis;
        }

        public static float FactorToSceneViewPixel(Transform trans, Vector3 localPos = new Vector3())
        {
#if UNITY_EDITOR
            UnityEditor.SceneView scene = UnityEditor.SceneView.currentDrawingSceneView;
            if (scene)
                return FactorToPixel(scene.camera, trans, localPos);
            else
                return 0f;
#else
        return 0f;
#endif

        }

        public static Camera GetSceneViewCamera()
        {
#if UNITY_EDITOR
            UnityEditor.SceneView scene = UnityEditor.SceneView.currentDrawingSceneView;
            if (scene)
                return scene.camera;
            else
                return null;
#else
            return null;
#endif
        }

        public static float FactorFromSceneViewPixel(Transform trans, Vector3 localPos = new Vector3())
        {
#if UNITY_EDITOR
            UnityEditor.SceneView scene = UnityEditor.SceneView.currentDrawingSceneView;
            if (scene)
                return FactorFromPixel(scene.camera, trans, localPos);
            else
                return 0f;
#else
        return 0f;
#endif
        }

    }
}